﻿//generation of moves
namespace FSharpChess

module Move_gen =
    let gen_captures pcid psn =
        let ans = new ResizeArray<Move>()
        ans.Capacity<-8
        let pc = psn.Pcs.[pcid]
        let pccol = pc.pcIsWhite
        let pcsqord = pc.SquareOrd
        //ray based captures
        let rec findMovesRays r rl =
            match r with
                |[] -> if rl<>[] then findMovesRays rl.Head rl.Tail
                | r ->  
                    let cord = r.Head 
                    if psn.Sqs.[cord] = -1 then
                        findMovesRays r.Tail rl 
                    elif (psn.Pcs.[psn.Sqs.[cord]].pcIsWhite <> pccol && psn.Pcs.[psn.Sqs.[cord]].Name<>enmName.King) then
                        ans.Add (psn.CreateMove(Standard, pcid, pc.SquareOrd, cord, psn.Sqs.[cord], 0))
                        if rl<>[] then findMovesRays rl.Head rl.Tail
                    else
                        if rl<>[] then findMovesRays rl.Head rl.Tail
        //do functions for captures
        let capOKt ord typ = 
            let pccapid = psn.Sqs.[ord]
            if pccapid <> -1 && psn.Pcs.[pccapid].pcIsWhite <> pccol && psn.Pcs.[pccapid].Name<>enmName.King then 
                ans.Add (psn.CreateMove(typ, pcid, pcsqord, ord, pccapid, 0))
        let capOK ord = capOKt ord Standard 
        //generate for each type of piece
        match pc.Name with
            |enmName.Pawn -> 
                let pr = Attack.getPlayer(pccol)
                let blnIsPromotion = if pccol then Ref.promPW.[pcsqord] else Ref.promPB.[pcsqord]
                if blnIsPromotion then
                    for ord in (if pccol then Ref.attsPW.[pcsqord] else Ref.attsPB.[pcsqord]) do capOKt ord PawnPromotionQueen
                else
                    List.iter capOK (if pccol then Ref.attsPW.[pcsqord] else Ref.attsPB.[pcsqord])
                    // En Passent Left
                    if (Ref.epPWl.[pcsqord] && pccol || Ref.epPBl.[pcsqord] && not pccol) then
                        let piecePassedid = psn.Sqs.[Ref.epPlpc.[pcsqord]]
                        if (piecePassedid <> -1 && psn.Pcs.[piecePassedid].NoOfMoves = 1 && psn.Pcs.[piecePassedid].LastMoveTurnNo = psn.Trn && psn.Pcs.[piecePassedid].Name = enmName.Pawn && psn.Pcs.[piecePassedid].pcIsWhite <> pc.pcIsWhite) then
                            let ord = if pccol then Ref.epPWlto.[pcsqord] else Ref.epPBlto.[pcsqord]
                            ans.Add (psn.CreateMove(EnPassent, pcid, pcsqord, ord, piecePassedid, 0))
                    // En Passent Right
                    if (Ref.epPWr.[pcsqord] && pccol || Ref.epPBr.[pcsqord] && not pccol) then
                        let piecePassedid = psn.Sqs.[Ref.epPrpc.[pcsqord]]
                        if (piecePassedid <> -1 && psn.Pcs.[piecePassedid].NoOfMoves = 1 && psn.Pcs.[piecePassedid].LastMoveTurnNo = psn.Trn && psn.Pcs.[piecePassedid].Name = enmName.Pawn && psn.Pcs.[piecePassedid].pcIsWhite <> pc.pcIsWhite) then
                            let ord = if pccol then Ref.epPWrto.[pcsqord] else Ref.epPBrto.[pcsqord]
                            ans.Add (psn.CreateMove(EnPassent, pcid, pc.SquareOrd, ord, piecePassedid, 0))
                ans
            |enmName.Bishop -> 
                findMovesRays Ref.raysB.[pcsqord].Head Ref.raysB.[pcsqord].Tail
                ans
            |enmName.Knight ->
                let ords = Ref.movsN.[pcsqord]
                List.iter capOK ords
                ans
            |enmName.Rook -> 
                findMovesRays Ref.raysR.[pcsqord].Head Ref.raysR.[pcsqord].Tail
                ans
            |enmName.Queen -> 
                findMovesRays Ref.raysQ.[pcsqord].Head Ref.raysQ.[pcsqord].Tail
                ans
            |enmName.King ->
                let ords = Ref.movsK.[pcsqord]
                List.iter capOK ords
                ans
            |_ -> raise InvalidName
    let gen_captures_pr pr psn =
        let ans = new ResizeArray<Move>()
        ans.Capacity<-80
        if pr.prIsWhite then
            for i = 0 to 15 do if psn.Pcs.[i].SquareOrd <> -1 then ans.AddRange(gen_captures i psn)
        else
            for i = 16 to 31 do if psn.Pcs.[i].SquareOrd <> -1 then ans.AddRange(gen_captures i psn)
        ans
    let gen_lazy_moves pcid psn =
        let ans = new ResizeArray<Move>()
        ans.Capacity<-30
        let pc = psn.Pcs.[pcid]
        let pccol = pc.pcIsWhite
        let pcsqord = pc.SquareOrd
        //ray based moves
        let rec findMovesRays r rl =
            match r with
                |[] -> if rl<>[] then findMovesRays rl.Head rl.Tail
                | r ->  
                    let cord = r.Head 
                    if psn.Sqs.[cord] = -1 then
                        ans.Add (psn.CreateMove(Standard, pcid, pc.SquareOrd, cord, -1, 0))
                        findMovesRays r.Tail rl 
                    elif (psn.Pcs.[psn.Sqs.[cord]].pcIsWhite <> pccol && psn.Pcs.[psn.Sqs.[cord]].Name<>enmName.King) then
                        ans.Add (psn.CreateMove(Standard, pcid, pc.SquareOrd, cord, psn.Sqs.[cord], 0))
                        if rl<>[] then findMovesRays rl.Head rl.Tail
                    else
                        if rl<>[] then findMovesRays rl.Head rl.Tail
        //do functions for OK moves, captures, non-captures
        let moveOK ord = 
            let pccapid = psn.Sqs.[ord]
            if pccapid = -1 || (psn.Pcs.[pccapid].pcIsWhite <> pccol && psn.Pcs.[pccapid].Name<>enmName.King) then 
                ans.Add (psn.CreateMove(Standard, pcid, pcsqord, ord, pccapid, 0))
        let capOKt ord typ = 
            let pccapid = psn.Sqs.[ord]
            if pccapid <> -1 && psn.Pcs.[pccapid].pcIsWhite <> pccol && psn.Pcs.[pccapid].Name<>enmName.King then 
                ans.Add (psn.CreateMove(typ, pcid, pcsqord, ord, pccapid, 0))
        let capOK ord = capOKt ord Standard 
        let noncapOKt ord typ = 
            let pccapid = psn.Sqs.[ord]
            if pccapid = -1 then 
                ans.Add (psn.CreateMove(typ, pcid, pcsqord, ord, pccapid, 0))
        let noncapOK ord = noncapOKt ord Standard 
        match pc.Name with
            |enmName.Pawn -> 
                let pr = Attack.getPlayer(pccol)
                let blnIsPromotion = if pccol then Ref.promPW.[pcsqord] else Ref.promPB.[pcsqord]
                if blnIsPromotion then
                    let PromotionTypes = [PawnPromotionQueen;PawnPromotionRook;PawnPromotionKnight;PawnPromotionBishop]
                    // Captures
                    for ord in (if pccol then Ref.attsPW.[pcsqord] else Ref.attsPB.[pcsqord]) do List.iter (capOKt ord) PromotionTypes 
                    // Forward one
                    let ord = if pccol then Ref.movPW.[pcsqord] else Ref.movPB.[pcsqord]
                    List.iter (noncapOKt ord) PromotionTypes
                else
                    // Captures
                    List.iter capOK (if pccol then Ref.attsPW.[pcsqord] else Ref.attsPB.[pcsqord])
                    // Forward one
                    let ord = if pccol then Ref.movPW.[pcsqord] else Ref.movPB.[pcsqord]
                    noncapOK ord
                    // Forward two
                    let ord2 = if pccol then Ref.movPW2.[pcsqord] else Ref.movPB2.[pcsqord]
                    if ord2 <> -1 then
                        // Check one square ahead is not occupied
                        let pccapid = psn.Sqs.[ord]
                        if (pccapid = -1) then
                            noncapOK ord2
                    // En Passent Left
                    if (Ref.epPWl.[pcsqord] && pccol || Ref.epPBl.[pcsqord] && not pccol) then
                        let piecePassedid = psn.Sqs.[Ref.epPlpc.[pcsqord]]
                        if (piecePassedid <> -1 && psn.Pcs.[piecePassedid].NoOfMoves = 1 && psn.Pcs.[piecePassedid].LastMoveTurnNo = psn.Trn && psn.Pcs.[piecePassedid].Name = enmName.Pawn && psn.Pcs.[piecePassedid].pcIsWhite <> pc.pcIsWhite) then
                            let ord = if pccol then Ref.epPWlto.[pcsqord] else Ref.epPBlto.[pcsqord]
                            ans.Add (psn.CreateMove(EnPassent, pcid, pcsqord, ord, piecePassedid, 0))
                    // En Passent Right
                    if (Ref.epPWr.[pcsqord] && pccol || Ref.epPBr.[pcsqord] && not pccol) then
                        let piecePassedid = psn.Sqs.[Ref.epPrpc.[pcsqord]]
                        if (piecePassedid <> -1 && psn.Pcs.[piecePassedid].NoOfMoves = 1 && psn.Pcs.[piecePassedid].LastMoveTurnNo = psn.Trn && psn.Pcs.[piecePassedid].Name = enmName.Pawn && psn.Pcs.[piecePassedid].pcIsWhite <> pc.pcIsWhite) then
                            let ord = if pccol then Ref.epPWrto.[pcsqord] else Ref.epPBrto.[pcsqord]
                            ans.Add (psn.CreateMove(EnPassent, pcid, pc.SquareOrd, ord, piecePassedid, 0))
                ans
            |enmName.Bishop -> 
                findMovesRays Ref.raysB.[pcsqord].Head Ref.raysB.[pcsqord].Tail
                ans
            |enmName.Knight ->
                let ords = Ref.movsN.[pcsqord]
                List.iter moveOK ords
                ans
            |enmName.Rook -> 
                findMovesRays Ref.raysR.[pcsqord].Head Ref.raysR.[pcsqord].Tail
                ans
            |enmName.Queen -> 
                findMovesRays Ref.raysQ.[pcsqord].Head Ref.raysQ.[pcsqord].Tail
                ans
            |enmName.King ->
                let ords = Ref.movsK.[pcsqord]
                List.iter moveOK ords
                if (pccol && Move_check.CanCastleK(psn)) then ans.Add(psn.CreateMove(CastleKingSide, pcid, pcsqord, pcsqord + 2, -1, 0))
                if (not pccol && Move_check.CanCastlek(psn)) then ans.Add(psn.CreateMove(CastleKingSide, pcid, pcsqord, pcsqord + 2, -1, 0))
                if (pccol && Move_check.CanCastleQ(psn)) then ans.Add(psn.CreateMove(CastleQueenSide, pcid, pcsqord, pcsqord - 2, -1, 0))
                if (not pccol && Move_check.CanCastleq(psn)) then ans.Add(psn.CreateMove(CastleQueenSide, pcid, pcsqord, pcsqord - 2, -1, 0))
                ans
            |_ -> raise InvalidName
    let gen_lazy_moves_pr pr psn =
        let ans = new ResizeArray<Move>()
        ans.Capacity<-80
        if pr.prIsWhite then
            for i = 0 to 15 do if psn.Pcs.[i].SquareOrd <> -1 then ans.AddRange(gen_lazy_moves i psn)
        else
            for i = 16 to 31 do if psn.Pcs.[i].SquareOrd <> -1 then ans.AddRange(gen_lazy_moves i psn)
        ans
    let GenerateLegalMoves(pc,psn) =
        let mvl = gen_lazy_moves pc psn
        for intIndex = (mvl.Count - 1) downto 0 do
            let move = mvl.[intIndex]
            if Move_do.DoMoveOK(move,psn) then
                Move_do.Undo(move,psn)
            else
                mvl.RemoveAt(intIndex)
        mvl
    let GenerateLegalMovesPr psn =
        let mvs = new ResizeArray<Move>()
        if psn.PlayerToPlay.prIsWhite then
            for i = 0 to 15 do
                if psn.Pcs.[i].SquareOrd <> -1 then mvs.AddRange(GenerateLegalMoves(i,psn))
        else
            for i = 16 to 31 do
                if psn.Pcs.[i].SquareOrd <> -1 then mvs.AddRange(GenerateLegalMoves(i,psn))
        mvs
    let IsInCheckMate psn =
        //this is only called if in check is known
        //check for any pc
        let checkpc pc =
            let mvl = gen_lazy_moves pc psn
            let rec validmv i =
                if i=mvl.Count then
                    false
                else
                    let move = mvl.[i]
                    if Move_do.DoMoveOK(move,psn) then
                        Move_do.Undo(move,psn)
                        true                        
                    else
                        validmv (i+1)
            validmv 0
        //function that returns if a legal move is found
        let bot,top = if psn.PlayerToPlay.prIsWhite then 0,15 else 16,31
        let rec nolegalexists i =
            if i>top then 
                true
            elif psn.Pcs.[i].SquareOrd <> -1 then 
                if checkpc i then
                    false 
                else 
                    nolegalexists (i+1)
            else
                nolegalexists (i+1)
        nolegalexists bot
    let do_checks psn =
        //get king details
        let kingisW = psn.PlayerToPlay.prIsWhite
        let kingid = if kingisW then 4 else 20
        let kingpc = psn.Pcs.[kingid]
        let kingsq = kingpc.SquareOrd
        //find move based
        //move based results
        let rec GetMchecks nm ml attl =
            match ml with
                |[] -> attl
                | ml ->  
                    let cord = ml.Head 
                    if psn.Sqs.[cord] = -1 then
                        GetMchecks nm ml.Tail attl
                    elif psn.Pcs.[psn.Sqs.[cord]].pcIsWhite <> kingisW && psn.Pcs.[psn.Sqs.[cord]].Name=nm then
                        GetMchecks nm ml.Tail ((cord::[])::attl)
                    else
                        GetMchecks nm ml.Tail attl
        // Pawn
        let Pchecks = GetMchecks enmName.Pawn (if kingisW then Ref.attsPBto.[kingsq] else Ref.attsPWto.[kingsq])[]
        // Knight
        let Nchecks = GetMchecks enmName.Knight Ref.movsN.[kingsq] []
        //get rays based results
        let rec GetRChecks nm r rl r2 attl =
            match r with
                |[] -> if rl=[] then attl else GetRChecks nm rl.Head rl.Tail [] attl
                | r ->  
                    let cord = r.Head 
                    if psn.Sqs.[cord] = -1 then
                        GetRChecks nm r.Tail rl (cord::r2) attl 
                    elif psn.Pcs.[psn.Sqs.[cord]].pcIsWhite <> kingisW && (psn.Pcs.[psn.Sqs.[cord]].Name=nm || psn.Pcs.[psn.Sqs.[cord]].Name=enmName.Queen ) then
                        let attladd = (List.rev (cord::r2))::attl
                        if rl=[] then attladd else GetRChecks nm rl.Head rl.Tail [] attladd
                    else
                        if rl=[] then attl else GetRChecks nm rl.Head rl.Tail [] attl
        // Bishop & Queen
        let Bchecks = GetRChecks enmName.Bishop Ref.raysB.[kingsq].Head Ref.raysB.[kingsq].Tail [] []
        // Rook & Queen
        let Rchecks = GetRChecks enmName.Rook Ref.raysR.[kingsq].Head Ref.raysR.[kingsq].Tail [] []
        Pchecks@Nchecks@Bchecks@Rchecks
    let gen_evasions_K pr psn checks =
        let ans = new ResizeArray<Move>()
        ans.Capacity<-8
        //set king vars
        let kingcol = pr.prIsWhite
        let kingid = if kingcol then 4 else 20
        let kingord = psn.Pcs.[kingid].SquareOrd
        let canmoveto ord =
            let rec canmovetorec ord il =
                if il=[] then
                    true
                elif List.length il.Head = 1 || il.Head.[0] <> ord then
                    canmovetorec ord il.Tail
                else
                    false
            canmovetorec ord checks
        let moveOK ord = 
            if canmoveto ord then
                let pccapid = psn.Sqs.[ord]
                if pccapid = -1 || (psn.Pcs.[pccapid].pcIsWhite <> kingcol && psn.Pcs.[pccapid].Name<>enmName.King) then 
                    ans.Add (psn.CreateMove(Standard, kingid, kingord, ord, pccapid, 0))
        let ords = Ref.movsK.[kingord]
        List.iter moveOK ords
        ans
    let gen_evasions_P pr psn (checks:int list list) =
        let ans = new ResizeArray<Move>()
        ans.Capacity<-8
        //set king vars
        let kingcol = pr.prIsWhite
        let kingid = if kingcol then 4 else 20
        let kingord = psn.Pcs.[kingid].SquareOrd
        let rec Pmoveto ordl =
            if List.length ordl = 1 then
                //last one do attacks
                let ordsfrom = if kingcol then Ref.attsPWto.[ordl.Head] else Ref.attsPBto.[ordl.Head]
                for ordf in ordsfrom do
                    let pcidfrom = psn.Sqs.[ordf]
                    if pcidfrom <> -1 && psn.Pcs.[pcidfrom].pcIsWhite = kingcol && psn.Pcs.[pcidfrom].Name=enmName.Pawn then
                        ans.Add (psn.CreateMove(Standard, pcidfrom, ordf, ordl.Head, psn.Sqs.[ordl.Head], 0))
            else
                let ordsfrom = if kingcol then Ref.movsPWto.[ordl.Head] else Ref.movsPBto.[ordl.Head]
                for ordf in ordsfrom do
                    let pcidfrom = psn.Sqs.[ordf]
                    if pcidfrom <> -1 && psn.Pcs.[pcidfrom].pcIsWhite = kingcol && psn.Pcs.[pcidfrom].Name=enmName.Pawn then
                        ans.Add (psn.CreateMove(Standard, pcidfrom, ordf, ordl.Head, -1, 0))
                Pmoveto ordl.Tail
        Pmoveto checks.[0]
        ans
    let gen_evasions_N pr psn (checks:int list list) =
        let ans = new ResizeArray<Move>()
        ans.Capacity<-8
        //set king vars
        let kingcol = pr.prIsWhite
        let kingid = if kingcol then 4 else 20
        let kingord = psn.Pcs.[kingid].SquareOrd
        let rec Nmoveto (ordl:int list) =
            let ordsfrom = Ref.movsN.[ordl.Head]
            for ordf in ordsfrom do
                let pcidfrom = psn.Sqs.[ordf]
                if pcidfrom <> -1 && psn.Pcs.[pcidfrom].pcIsWhite = kingcol && psn.Pcs.[pcidfrom].Name=enmName.Knight then
                    ans.Add (psn.CreateMove(Standard, pcidfrom, ordf, ordl.Head, psn.Sqs.[ordl.Head], 0))
            if List.length ordl > 1 then
                Nmoveto ordl.Tail
        Nmoveto checks.[0]
        ans
    let gen_evasions_B pr psn (checks:int list list) =
        let ans = new ResizeArray<Move>()
        ans.Capacity<-8
        //set king vars
        let kingcol = pr.prIsWhite
        let kingid = if kingcol then 4 else 20
        let kingord = psn.Pcs.[kingid].SquareOrd
        let rec Bmoveto (ordl:int list) =
            let raysfrom = Ref.raysB.[ordl.Head]
            for rays in raysfrom do
                let rec doBray (il:int list) =
                    let pcidfrom = psn.Sqs.[il.Head]
                    if pcidfrom = -1 then
                        if il.Tail<>[] then doBray il.Tail
                    else
                        if psn.Pcs.[pcidfrom].pcIsWhite = kingcol && (psn.Pcs.[pcidfrom].Name=enmName.Bishop||psn.Pcs.[pcidfrom].Name=enmName.Queen) then
                            ans.Add (psn.CreateMove(Standard, pcidfrom, il.Head, ordl.Head, psn.Sqs.[ordl.Head], 0))
                if rays<>[] then doBray rays
            if List.length ordl > 1 then
                Bmoveto ordl.Tail
        Bmoveto checks.[0]
        ans
    let gen_evasions_R pr psn (checks:int list list) =
        let ans = new ResizeArray<Move>()
        ans.Capacity<-8
        //set king vars
        let kingcol = pr.prIsWhite
        let kingid = if kingcol then 4 else 20
        let kingord = psn.Pcs.[kingid].SquareOrd
        let rec Rmoveto (ordl:int list) =
            let raysfrom = Ref.raysR.[ordl.Head]
            for rays in raysfrom do
                let rec doRray (il:int list) =
                    let pcidfrom = psn.Sqs.[il.Head]
                    if pcidfrom = -1 then
                        if il.Tail<>[] then doRray il.Tail
                    else
                        if psn.Pcs.[pcidfrom].pcIsWhite = kingcol && (psn.Pcs.[pcidfrom].Name=enmName.Rook||psn.Pcs.[pcidfrom].Name=enmName.Queen) then
                            ans.Add (psn.CreateMove(Standard, pcidfrom, il.Head, ordl.Head, psn.Sqs.[ordl.Head], 0))
                if rays<>[] then doRray rays
            if List.length ordl > 1 then
                Rmoveto ordl.Tail
        Rmoveto checks.[0]
        ans
    let gen_evasions_pr pr psn checks =
        let ans = new ResizeArray<Move>()
        ans.Capacity<-40
        //start with king moves
        let kingcol = pr.prIsWhite
        let kingid = if kingcol then 4 else 20
        let kingord = psn.Pcs.[kingid].SquareOrd
        //get king evasions
        ans.AddRange(gen_evasions_K pr psn checks)
        //that is it if in double check
        if List.length checks < 2 then
            //pawns
            ans.AddRange(gen_evasions_P pr psn checks)
            //Knights
            ans.AddRange(gen_evasions_N pr psn checks)
            //Bishops and Queen
            ans.AddRange(gen_evasions_B pr psn checks)
            //Rooks and Queen
            ans.AddRange(gen_evasions_R pr psn checks)
        ans
    let in_mate_quick pr psn checks =
        let rec novalidmv (mvl:ResizeArray<Move>) i =
            if i=mvl.Count then
                true
            else
                let move = mvl.[i]
                if Move_do.DoMoveOK(move,psn) then
                    Move_do.Undo(move,psn)
                    false                        
                else
                    novalidmv mvl (i+1)
        let noKmove() = novalidmv (gen_evasions_K pr psn checks) 0
        let noRmove() = novalidmv (gen_evasions_R pr psn checks) 0
        let noBmove() = novalidmv (gen_evasions_B pr psn checks) 0
        let noNmove() = novalidmv (gen_evasions_N pr psn checks) 0
        let noPmove() = novalidmv (gen_evasions_P pr psn checks) 0
        noKmove()&&noRmove()&&noBmove()&&noNmove()&&noPmove()
